/* ==============================================================================
*
*   Copyright (c) 2010, Pierre Favier
*   All rights reserved.
*
*   NTasking is free software: you can redistribute it and/or modify it
*   under the terms of the GNU General Public License as published by the
*   Free Software Foundation.
*
*   Redistribution and use in source and binary forms, with or without
*   modification, are permitted provided that the following conditions
*   are met:
*
*   -   Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*
*   -   Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials
*       provided with the distribution.
*
*   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
*   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
*   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
*   BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
*   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
*   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
*   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
*   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
*   TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
*   THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
*   SUCH DAMAGE.
*
* ============================================================================== */

using System;
using System.Collections.Generic;
using NUnit.Framework;
using Rhino.Mocks;
using Rhino.Mocks.Constraints;

namespace NTasking.ValidationTests.Other
{

    [TestFixture]
    public class TaskDeclaration : ValidationTest
    {

        internal class TaskEntryDeclaration : ValidationTestTask
        {

            [Entry]
            internal Action NotUsed { get; private set; }

            [Entry]
            internal Action Put0 { get; private set; }
            [Entry]
            internal Action<int> Put1 { get; private set; }
            [Entry]
            internal Action<int, int> Put2 { get; private set; }
            [Entry]
            internal Action<int, int, int> Put3 { get; private set; }
            [Entry]
            internal Action<int, int, int, int> Put4 { get; private set; }


            [Entry]
            internal Func<bool> Get0 { get; private set; }
            [Entry]
            internal Func<int, bool> Get1 { get; private set; }
            [Entry]
            internal Func<int, int, bool> Get2 { get; private set; }
            [Entry]
            internal Func<int, int, int, bool> Get3 { get; private set; }
            [Entry]
            internal Func<int, int, int, int, bool> Get4 { get; private set; }


            [Entry]
            internal ActionFamily<double> FPut0 { get; private set; }
            [Entry]
            internal ActionFamily<double, int> FPut1 { get; private set; }
            [Entry]
            internal ActionFamily<double, int, int> FPut2 { get; private set; }
            [Entry]
            internal ActionFamily<double, int, int, int> FPut3 { get; private set; }
            [Entry]
            internal ActionFamily<double, int, int, int, int> FPut4 { get; private set; }


            [Entry]
            internal FuncFamily<double, bool> FGet0 { get; private set; }
            [Entry]
            internal FuncFamily<double, int, bool> FGet1 { get; private set; }
            [Entry]
            internal FuncFamily<double, int, int, bool> FGet2 { get; private set; }
            [Entry]
            internal FuncFamily<double, int, int, int, bool> FGet3 { get; private set; }
            [Entry]
            internal FuncFamily<double, int, int, int, int, bool> FGet4 { get; private set; }


            internal TaskEntryDeclaration(IValidationTestResult result)
                : base(result)
            {
            }


            protected override void Body()
            {

                Select(s => s.
                    When(() => true).
                        Accept(FPut0, (i) => { }).
                Or.
                    When(() => true).
                        Accept(FPut1, (i, p1) => { }).
                Or.
                    When(() => true).
                        Accept(FPut2, (i, p1, p2) => { }).
                Or.
                    When(() => true).
                        Accept(FPut3, (i, p1, p2, p3) => { }).
                Or.
                    When(() => true).
                        Accept(FPut4, (i, p1, p2, p3, p4) => { }).
                Or.
                    When(() => true).
                        Accept(FGet0, (i) => true).
                Or.
                    When(() => true).
                        Accept(FGet1, (i, p1) => true).
                Or.
                    When(() => true).
                        Accept(FGet2, (i, p1, p2) => true).
                Or.
                    When(() => true).
                        Accept(FGet3, (i, p1, p2, p3) => true).
                Or.
                    When(() => true).
                        Accept(FGet4, (i, p1, p2, p3, p4) => true).


                Or.
                    When(() => true).
                        Accept(FPut0[0.0], (double i) => { }).
                Or.
                    When(() => true).
                        Accept(FPut1[0.0], (double i, int p1) => { }).
                Or.
                    When(() => true).
                        Accept(FPut2[0.0], (double i, int p1, int p2) => { }).
                Or.
                    When(() => true).
                        Accept(FPut3[0.0], (double i, int p1, int p2, int p3) => { }).
                Or.
                    When(() => true).
                        Accept(FPut4[0.0], (double i, int p1, int p2, int p3, int p4) => { }).
                Or.
                    When(() => true).
                        Accept(FGet0[0.0], (double i) => true).
                Or.
                    When(() => true).
                        Accept(FGet1[0.0], (double i, int p1) => true).
                Or.
                    When(() => true).
                        Accept(FGet2[0.0], (double i, int p1, int p2) => true).
                Or.
                    When(() => true).
                        Accept(FGet3[0.0], (double i, int p1, int p2, int p3) => true).
                Or.
                    When(() => true).
                        Accept(FGet4[0.0], (double i, int p1, int p2, int p3, int p4) => true).


                Or.
                    Terminate
                );


                Accept(Put0, () => { }, null);
                Accept(Put1, (p1) => { }, null);
                Accept(Put2, (p1, p2) => { }, null);
                Accept(Put3, (p1, p2, p3) => { }, null);
                Accept(Put4, (p1, p2, p3, p4) => { }, null);
                Accept(Get0, () => true, null);
                Accept(Get1, (p1) => true, null);
                Accept(Get2, (p1, p2) => true, null);
                Accept(Get3, (p1, p2, p3) => true, null);
                Accept(Get4, (p1, p2, p3, p4) => true, null);
                Accept(FPut0, (i) => { }, null);
                Accept(FPut1, (i, p1) => { }, null);
                Accept(FPut2, (i, p1, p2) => { }, null);
                Accept(FPut3, (i, p1, p2, p3) => { }, null);
                Accept(FPut4, (i, p1, p2, p3, p4) => { }, null);
                Accept(FGet0, (i) => true, null);
                Accept(FGet1, (i, p1) => true, null);
                Accept(FGet2, (i, p1, p2) => true, null);
                Accept(FGet3, (i, p1, p2, p3) => true, null);
                Accept(FGet4, (i, p1, p2, p3, p4) => true, null);

                Accept(FPut0[0.0], (double i) => { }, null);
                Accept(FPut1[0.0], (double i, int p1) => { }, null);
                Accept(FPut2[0.0], (double i, int p1, int p2) => { }, null);
                Accept(FPut3[0.0], (double i, int p1, int p2, int p3) => { }, null);
                Accept(FPut4[0.0], (double i, int p1, int p2, int p3, int p4) => { }, null);
                Accept(FGet0[0.0], (double i) => true, null);
                Accept(FGet1[0.0], (double i, int p1) => true, null);
                Accept(FGet2[0.0], (double i, int p1, int p2) => true, null);
                Accept(FGet3[0.0], (double i, int p1, int p2, int p3) => true, null);
                Accept(FGet4[0.0], (double i, int p1, int p2, int p3, int p4) => true, null);

                while (true)
                {

                    Select(s => s.
                        When(() => true).
                            Accept(Put0).
                    Or.
                        When(() => true).
                            Accept(Put1).
                    Or.
                        When(() => true).
                            Accept(Put2).
                    Or.
                        When(() => true).
                            Accept(Put3).
                    Or.
                        When(() => true).
                            Accept(Put4).
                    Or.
                        When(() => true).
                            Accept(Get0, () => true).
                    Or.
                        When(() => true).
                            Accept(Get1, (p1) => true).
                    Or.
                        When(() => true).
                            Accept(Get2, (p1, p2) => true).
                    Or.
                        When(() => true).
                            Accept(Get3, (p1, p2, p3) => true).
                    Or.
                        When(() => true).
                            Accept(Get4, (p1, p2, p3, p4) => true).


                    Or.
                        When((double i) => true).
                            Accept(FPut0, (i) => { }).
                    Or.
                        When((double i) => true).
                            Accept(FPut1, (i, p1) => { }).
                    Or.
                        When((double i) => true).
                            Accept(FPut2, (i, p1, p2) => { }).
                    Or.
                        When((double i) => true).
                            Accept(FPut3, (i, p1, p2, p3) => { }).
                    Or.
                        When((double i) => true).
                            Accept(FPut4, (i, p1, p2, p3, p4) => { }).
                    Or.
                        When((double i) => true).
                            Accept(FGet0, (i) => true).
                    Or.
                        When((double i) => true).
                            Accept(FGet1, (i, p1) => true).
                    Or.
                        When((double i) => true).
                            Accept(FGet2, (i, p1, p2) => true).
                    Or.
                        When((double i) => true).
                            Accept(FGet3, (i, p1, p2, p3) => true).
                    Or.
                        When((double i) => true).
                            Accept(FGet4, (i, p1, p2, p3, p4) => true).


                    Or.
                        Terminate
                    );

                }


            }

        }


        internal class TaskProcedureDeclaration : ValidationTestTask
        {

            [ReadWriteOperation]
            internal Action Put0 { get; private set; }


            internal TaskProcedureDeclaration(IValidationTestResult result)
                : base(result)
            {
            }


            protected override void Body()
            {
            }

        }


        internal class TaskFunctionDeclaration : ValidationTestTask
        {

            [ReadOnlyOperation]
            internal Func<bool> Get0 { get; private set; }


            internal TaskFunctionDeclaration(IValidationTestResult result)
                : base(result)
            {
            }


            protected override void Body()
            {
            }

        }


        [Test]
        public void EntryDeclaration()
        {
            TaskEntryDeclaration task = null;
            IValidationTestResult result = Mocks.DynamicMock<IValidationTestResult>();

            Ada.Declare(() =>
            {
                task = new TaskEntryDeclaration(result);

                // Setup text expectations

                Expect.Call(() => result.TaskFinished(null)).
                    IgnoreArguments().
                    Constraints(Is.Same(task));

                Mocks.ReplayAll();
            },
            Begin: () =>
            {
                // Start test execution

                task.FPut0[0.0](); //task.Put0();

                task.Put0();
                task.Put1(1);
                task.Put2(1, 2);
                task.Put3(1, 2, 3);
                task.Put4(1, 2, 3, 4);

                task.Get0();
                task.Get1(1);
                task.Get2(1, 2);
                task.Get3(1, 2, 3);
                task.Get4(1, 2, 3, 4);

                task.FPut0[0.0]();
                task.FPut1[0.0](1);
                task.FPut2[0.0](1, 2);
                task.FPut3[0.0](1, 2, 3);
                task.FPut4[0.0](1, 2, 3, 4);

                task.FGet0[0.0]();
                task.FGet1[0.0](1);
                task.FGet2[0.0](1, 2);
                task.FGet3[0.0](1, 2, 3);
                task.FGet4[0.0](1, 2, 3, 4);

                task.FPut0[0.0]();
                task.FPut1[0.0](1);
                task.FPut2[0.0](1, 2);
                task.FPut3[0.0](1, 2, 3);
                task.FPut4[0.0](1, 2, 3, 4);

                task.FGet0[0.0]();
                task.FGet1[0.0](1);
                task.FGet2[0.0](1, 2);
                task.FGet3[0.0](1, 2, 3);
                task.FGet4[0.0](1, 2, 3, 4);

                Ada.Select(s => s.
                    Call(task.Put0)().Then(() =>
                    {

                    }).
                OrDelay(1.0).Then(() =>
                    {
                        ;
                    }));
                Ada.Select(s => s.
                    Call(task.Put1)(1).Then(() =>
                    {
                        ;
                    }).
                OrDelay(1.0).Then(() =>
                    {
                        ;
                    }));
                Ada.Select(s => s.
                    Call(task.Put2)(1, 2).Then(() =>
                    {
                        ;
                    }).
                OrDelay(1.0).Then(() =>
                    {
                        ;
                    }));
                Ada.Select(s => s.
                    Call(task.Put3)(1, 2, 3).Then(() =>
                    {
                        ;
                    }).
                OrDelay(1.0).Then(() =>
                    {
                        ;
                    }));
                Ada.Select(s => s.
                    Call(task.Put4)(1, 2, 3, 4).Then(() =>
                    {
                        ;
                    }).
                OrDelay(1.0).Then(() =>
                    {
                        ;
                    }));

                Ada.Select(s => s.
                    Call(task.Get0)().Then(res =>
                    {
                        ;
                    }).
                OrDelay(1.0).Then(() =>
                    {
                        ;
                    }));
                Ada.Select(s => s.
                    Call(task.Get1)(1).Then(res =>
                    {
                        ;
                    }).
                OrDelay(1.0).Then(() =>
                    {
                        ;
                    }));
                Ada.Select(s => s.
                    Call(task.Get2)(1, 2).Then(res =>
                    {
                        ;
                    }).
                OrDelay(1.0).Then(() =>
                    {
                        ;
                    }));
                Ada.Select(s => s.
                    Call(task.Get3)(1, 2, 3).Then(res =>
                    {
                        ;
                    }).
                OrDelay(1.0).Then(() =>
                    {
                        ;
                    }));
                Ada.Select(s => s.
                    Call(task.Get4)(1, 2, 3, 4).Then(res =>
                    {
                        ;
                    }).
                OrDelay(1.0).Then(() =>
                    {
                        ;
                    }));

                Ada.Select(s => s.
                    Call(task.FPut0[0.0])().Then(() =>
                    {
                        ;
                    }).
                OrDelay(1.0).Then(() =>
                    {
                        ;
                    }));
                Ada.Select(s => s.
                    Call(task.FPut1[0.0])(1).Then(() =>
                    {
                        ;
                    }).
                OrDelay(1.0).Then(() =>
                    {
                        ;
                    }));
                Ada.Select(s => s.
                    Call(task.FPut2[0.0])(1, 2).Then(() =>
                    {
                        ;
                    }).
                OrDelay(1.0).Then(() =>
                    {
                        ;
                    }));
                Ada.Select(s => s.
                    Call(task.FPut3[0.0])(1, 2, 3).Then(() =>
                    {
                        ;
                    }).
                OrDelay(1.0).Then(() =>
                    {
                        ;
                    }));
                Ada.Select(s => s.
                    Call(task.FPut4[0.0])(1, 2, 3, 4).Then(() =>
                    {
                        ;
                    }).
                OrDelay(1.0).Then(() =>
                    {
                        ;
                    }));

                Ada.Select(s => s.
                    Call(task.FGet0[0.0])().Then(res =>
                    {
                        ;
                    }).
                OrDelay(1.0).Then(() =>
                    {
                        ;
                    }));
                Ada.Select(s => s.
                    Call(task.FGet1[0.0])(1).Then(res =>
                    {
                        ;
                    }).
                OrDelay(new TimeSpan(0, 0, 0, 1)).Then(() =>
                    {
                        ;
                    }));
                Ada.Select(s => s.
                    Call(task.FGet2[0.0])(1, 2).Then(res =>
                    {
                        ;
                    }).
                OrDelay(DateTime.Now.AddSeconds(1)).Then(() =>
                    {
                        ;
                    }));
                Ada.Select(s => s.
                    Call(task.FGet3[0.0])(1, 2, 3).Then(res =>
                    {
                        ;
                    }).
                OrDelay(1.0).Then(() =>
                    {
                        ;
                    }));
                Ada.Select(s => s.
                    Call(task.FGet4[0.0])(1, 2, 3, 4).Then(res =>
                    {
                        ;
                    }).
                OrDelay(1000).Then(() =>
                    {
                        ;
                    }));

                Ada.Select(s => s.
                    Delay(new TimeSpan(0, 0, 0, 1)).
                ThenAbort(() =>
                {
                    task.FGet4[0.0](1, 2, 3, 4);
                }));

                Ada.Select(s => s.
                    Delay(1000).
                ThenAbort(() =>
                {
                    task.FGet4[0.0](1, 2, 3, 4);
                }));

                Ada.Select(s => s.
                    Call(task.NotUsed)().
                Else(() =>
                {
                    ;
                }));

                List<Future> futures = new List<Future>();
                Future aFuture;
                Future<bool> fFuture;

                fFuture = task.FGet4[0.0].Async(0, 0, 0, 0);
                fFuture.Result();

                futures.Add(task.Put0.Async());
                futures.Add(task.Put1.Async(0));
                futures.Add(task.Put2.Async(0, 0));
                futures.Add(task.Put3.Async(0, 0, 0));
                futures.Add(task.Put4.Async(0, 0, 0, 0));

                futures.Add(task.Get0.Async());
                futures.Add(task.Get1.Async(0));
                futures.Add(task.Get2.Async(0, 0));
                futures.Add(task.Get3.Async(0, 0, 0));
                futures.Add(task.Get4.Async(0, 0, 0, 0));

                futures.Add(task.FPut0[0.0].Async());
                futures.Add(task.FPut1[0.0].Async(0));
                futures.Add(task.FPut2[0.0].Async(0, 0));
                futures.Add(task.FPut3[0.0].Async(0, 0, 0));
                futures.Add(task.FPut4[0.0].Async(0, 0, 0, 0));

                futures.Add(task.FGet0[0.0].Async());
                futures.Add(task.FGet1[0.0].Async(0));
                futures.Add(task.FGet2[0.0].Async(0, 0));
                futures.Add(task.FGet3[0.0].Async(0, 0, 0));
                futures.Add(task.FGet4[0.0].Async(0, 0, 0, 0));

                aFuture = Future.WaitAny(futures);
                futures.Remove(aFuture);

                Future.WaitAll(futures);
            });
        }


        [Test]
        public void ProcedureDeclaration()
        {
            TaskProcedureDeclaration task;
            IValidationTestResult result = Mocks.DynamicMock<IValidationTestResult>();

            Ada.Begin(() =>
            {
                // Setup text expectations

                // Start test execution

                Mocks.ReplayAll();

                try
                {
                    task = new TaskProcedureDeclaration(result);
                }
                catch (SyntaxError ex)
                {
                    if (ex.ErrorCode != Errors.ProtectedOperationNotAllowedInTaskError)
                        throw;
                }
            });
        }


        [Test]
        public void FunctionDeclaration()
        {
            TaskFunctionDeclaration task;
            IValidationTestResult result = Mocks.DynamicMock<IValidationTestResult>();

            Ada.Begin(() =>
            {
                // Setup text expectations

                // Start test execution

                Mocks.ReplayAll();

                try
                {
                    task = new TaskFunctionDeclaration(result);
                }
                catch (SyntaxError ex)
                {
                    if (ex.ErrorCode != Errors.ProtectedOperationNotAllowedInTaskError)
                        throw;
                }
            });
        }

    }

}
